// language: java
package com.shopping.shoppingdemo.utils;

import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.write.metadata.WriteSheet;
import com.shopping.shoppingdemo.domain.dto.RequestCountExcelDO;
import com.shopping.shoppingdemo.domain.po.RequestCountDO;
import com.shopping.shoppingdemo.domain.vo.PageParam;
import com.shopping.shoppingdemo.service.RequestCountService;
import com.shopping.shoppingdemo.utils.converter.StatisticsConverter;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class ParallelExportUtil {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ParallelExportUtil.class);

    public static void export(RequestCountService requestCountService,// 查询数据的 service
                              long total,// 总数据量
                              int pageSize,// 每页大小
                              int sheetSize,// 每个 sheet 包含的数据量
                              OutputStream outputStream,// 输出流(响应到浏览器或写入文件)
                              int fetchThreads) throws IOException {// 并发查询线程数
        int sheetCount = (int) ((total + sheetSize - 1) / sheetSize);//计算 sheet 数量
        //总数据 total = 5_000_000
        //每个 sheet 存 1_000_000
        //sheetCount = 5

        ThreadFactory threadFactory = new ThreadFactory() {// 自定义线程工厂
            private final AtomicInteger idx = new AtomicInteger(1);// 线程编号

            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r, "export-fetch-" + idx.getAndIncrement());// 自定义线程名称
                t.setDaemon(false);// 非守护线程
                return t;// 返回新建的线程
            }
        };
        //队列容量，避免过多任务堆积
        int queueCapacity = Math.max(4, fetchThreads * 2);// 最小容量 4 ，否则可能太小导致频繁拒绝任务
        ThreadPoolExecutor fetchPool = new ThreadPoolExecutor(
                fetchThreads,// 核心线程数 corePoolSize 为 CPU 核心数的两倍 【传来的数为8】
                fetchThreads,// 最大线程数
                60L, TimeUnit.SECONDS,// 空闲线程存活时间
                new ArrayBlockingQueue<>(queueCapacity),// 有界队列
                threadFactory,// 使用自定义线程工厂
                new ThreadPoolExecutor.CallerRunsPolicy()// 饱和策略：调用者运行
        );

        //使用 FastExcel（高性能版 EasyExcel）流式写入，不会一次性加载所有数据到内存。
        try (ExcelWriter excelWriter = FastExcel.write(outputStream, RequestCountExcelDO.class).build()) {
            for (int sheetIndex = 0; sheetIndex < sheetCount; sheetIndex++) {
                // 计算当前 sheet 的数据范围
                long sheetOffset = (long) sheetIndex * sheetSize;
                // 计算当前 sheet 实际数据量
                int currentSheetSize = (int) Math.min(sheetSize, Math.max(0, total - sheetOffset));
                // 计算当前 sheet 包含的页数
                int pagesInThisSheet = (currentSheetSize + pageSize - 1) / pageSize;
                if (pagesInThisSheet <= 0) {
                    continue;
                }
                // 创建 sheet
                WriteSheet sheet = FastExcel.writerSheet(sheetIndex, "sheet" + (sheetIndex + 1)).build();
                // 并发查询每页数据并写入 sheet
                List<Future<List<RequestCountExcelDO>>> futures = new ArrayList<>(pagesInThisSheet);
                // 使用 CountDownLatch 用于同步主线程等待任务完成。
                CountDownLatch latch = new CountDownLatch(pagesInThisSheet);

                for (int pageIndex = 0; pageIndex < pagesInThisSheet; pageIndex++) {
                    final int pageStart = (int) (sheetOffset + (long) pageIndex * pageSize) + 1;
                    Callable<List<RequestCountExcelDO>> task = () -> {// 定义查询任务
                        try {
                            // 查询数据
                            List<RequestCountDO> data = requestCountService.listRequestCount(PageParam.newPageInstance(pageStart, pageSize));
                            // 转换数据
                            return StatisticsConverter.convertToRequestCountExcelDOList(data);
                        } finally {
                            latch.countDown();// 任务完成，计数器减一
                        }
                    };
                    futures.add(fetchPool.submit(task));//每个任务提交给线程池异步执行，并收集到 futures 列表中。
                }

                // 主线程等待所有查询任务完成后，按顺序写入数据到 Excel。
                try {
                    for (int i = 0; i < futures.size(); i++) {
                        Future<List<RequestCountExcelDO>> f = futures.get(i);// 获取每个任务的 Future
                        List<RequestCountExcelDO> list = f.get();// 等待任务完成并获取结果
                        if (list != null && !list.isEmpty()) {// 写入数据到 Excel
                            excelWriter.write(list, sheet);// 按页顺序写入，保证数据顺序正确
                        }

                        // --- 新增两条日志（按页顺序打印） ---
                        int pageIndex = i;
                        long offset = sheetOffset + (long) pageIndex * pageSize + 1;

                        long usedMb = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024;
                        long freeMb = Runtime.getRuntime().freeMemory() / 1024 / 1024;
                        log.info("总共 {} 条，已经导出 {} 条. JVM 内存 used={}MB, free={}MB",
                                total, Math.min(offset + pageSize - 1, total), usedMb, freeMb);
                        // --- 日志结束 ---
                    }
                    latch.await();// 确保所有任务完成
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();// 恢复中断状态
                    throw new IOException("导出被中断", e);
                } catch (ExecutionException e) {
                    throw new IOException("导出并发查询失败", e.getCause());
                }
            }
        } finally {
            fetchPool.shutdown();// 关闭线程池
            try {
                if (!fetchPool.awaitTermination(30, TimeUnit.SECONDS)) {// 等待线程池任务完成
                    fetchPool.shutdownNow();// 超时强制关闭
                }
            } catch (InterruptedException ignored) {// 忽略中断
                fetchPool.shutdownNow();// 强制关闭
                Thread.currentThread().interrupt();// 恢复中断状态
            }
        }
    }
}
